1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 import java.applet.Applet;
34 import java.awt.AWTEvent;
35 import java.awt.BorderLayout;
36 import java.awt.Button;
37 import java.awt.Canvas;
38 import java.awt.Choice;
39 import java.awt.Color;
40 import java.awt.Dimension;
41 import java.awt.FlowLayout;
42 import java.awt.FontMetrics;
43 import java.awt.Frame;
44 import java.awt.Graphics;
45 import java.awt.Image;
46 import java.awt.Label;
47 import java.awt.LayoutManager;
48 import java.awt.Panel;
49 import java.awt.TextField;
50 import java.awt.Toolkit;
51 import java.awt.event.ActionEvent;
52 import java.awt.event.ActionListener;
53 import java.awt.event.KeyEvent;
54 import java.awt.event.TextEvent;
55 import java.awt.image.ColorModel;
56 import java.awt.image.MemoryImageSource;
57
58
59 enum DitherMethod {
60
61 NOOP, RED, GREEN, BLUE, ALPHA, SATURATION
62 };
63
64
65 @SuppressWarnings("serial")
66 public class DitherTest extends Applet implements Runnable {
67
68 private Thread runner;
69 private DitherControls XControls;
70 private DitherControls YControls;
71 private DitherCanvas canvas;
72
73 public static void main(String args[]) {
74 Frame f = new Frame("DitherTest");
75 DitherTest ditherTest = new DitherTest();
76 ditherTest.init();
77 f.add("Center", ditherTest);
78 f.pack();
79 f.setVisible(true);
80 ditherTest.start();
81 }
82
83 @Override
84 public void init() {
85 String xspec = null, yspec = null;
86 int xvals[] = new int[2];
87 int yvals[] = new int[2];
88
89 try {
90 xspec = getParameter("xaxis");
91 yspec = getParameter("yaxis");
92 } catch (NullPointerException ignored) {
93
94 }
95
96 if (xspec == null) {
97 xspec = "red";
98 }
99 if (yspec == null) {
100 yspec = "blue";
101 }
102 DitherMethod xmethod = colorMethod(xspec, xvals);
103 DitherMethod ymethod = colorMethod(yspec, yvals);
104
105 setLayout(new BorderLayout());
106 XControls = new DitherControls(this, xvals[0], xvals[1],
107 xmethod, false);
108 YControls = new DitherControls(this, yvals[0], yvals[1],
109 ymethod, true);
110 YControls.addRenderButton();
111 add("North", XControls);
112 add("South", YControls);
113 add("Center", canvas = new DitherCanvas());
114 }
115
116 private DitherMethod colorMethod(String s, int vals[]) {
117 DitherMethod method = DitherMethod.NOOP;
118 if (s == null) {
119 s = "";
120 }
121 String lower = s.toLowerCase();
122
123 for (DitherMethod m : DitherMethod.values()) {
124 if (lower.startsWith(m.toString().toLowerCase())) {
125 method = m;
126 lower = lower.substring(m.toString().length());
127 }
128 }
129 if (method == DitherMethod.NOOP) {
130 vals[0] = 0;
131 vals[1] = 0;
132 return method;
133 }
134 int begval = 0;
135 int endval = 255;
136 try {
137 int dash = lower.indexOf('-');
138 if (dash < 0) {
139 endval = Integer.parseInt(lower);
140 } else {
141 begval = Integer.parseInt(lower.substring(0, dash));
142 endval = Integer.parseInt(lower.substring(dash + 1));
143 }
144 } catch (NumberFormatException ignored) {
145 }
146
147 if (begval < 0) {
148 begval = 0;
149 } else if (begval > 255) {
150 begval = 255;
151 }
152
153 if (endval < 0) {
154 endval = 0;
155 } else if (endval > 255) {
156 endval = 255;
157 }
158
159 vals[0] = begval;
160 vals[1] = endval;
161 return method;
162 }
163
164
165
166
167
168 private Image calculateImage() {
169 Thread me = Thread.currentThread();
170
171 int width = canvas.getSize().width;
172 int height = canvas.getSize().height;
173 int xvals[] = new int[2];
174 int yvals[] = new int[2];
175 int xmethod = XControls.getParams(xvals);
176 int ymethod = YControls.getParams(yvals);
177 int pixels[] = new int[width * height];
178 int c[] = new int[4];
179 int index = 0;
180 for (int j = 0; j < height; j++) {
181 for (int i = 0; i < width; i++) {
182 c[0] = c[1] = c[2] = 0;
183 c[3] = 255;
184 if (xmethod < ymethod) {
185 applyMethod(c, xmethod, i, width, xvals);
186 applyMethod(c, ymethod, j, height, yvals);
187 } else {
188 applyMethod(c, ymethod, j, height, yvals);
189 applyMethod(c, xmethod, i, width, xvals);
190 }
191 pixels[index++] = ((c[3] << 24) | (c[0] << 16) | (c[1] << 8)
192 | c[2]);
193 }
194
195
196 if (runner != me) {
197 return null;
198 }
199 }
200 return createImage(new MemoryImageSource(width, height,
201 ColorModel.getRGBdefault(), pixels, 0, width));
202 }
203
204 private void applyMethod(int c[], int methodIndex, int step,
205 int total, int vals[]) {
206 DitherMethod method = DitherMethod.values()[methodIndex];
207 if (method == DitherMethod.NOOP) {
208 return;
209 }
210 int val = ((total < 2)
211 ? vals[0]
212 : vals[0] + ((vals[1] - vals[0]) * step / (total - 1)));
213 switch (method) {
214 case RED:
215 c[0] = val;
216 break;
217 case GREEN:
218 c[1] = val;
219 break;
220 case BLUE:
221 c[2] = val;
222 break;
223 case ALPHA:
224 c[3] = val;
225 break;
226 case SATURATION:
227 int max = Math.max(Math.max(c[0], c[1]), c[2]);
228 int min = max * (255 - val) / 255;
229 if (c[0] == 0) {
230 c[0] = min;
231 }
232 if (c[1] == 0) {
233 c[1] = min;
234 }
235 if (c[2] == 0) {
236 c[2] = min;
237 }
238 break;
239 }
240 }
241
242 @Override
243 public void start() {
244 runner = new Thread(this);
245 runner.start();
246 }
247
248 @Override
249 public void run() {
250 canvas.setImage(null);
251 Image img = calculateImage();
252 if (img != null && runner == Thread.currentThread()) {
253 canvas.setImage(img);
254 }
255 }
256
257 @Override
258 public void stop() {
259 runner = null;
260 }
261
262 @Override
263 public void destroy() {
264 remove(XControls);
265 remove(YControls);
266 remove(canvas);
267 }
268
269 @Override
270 public String getAppletInfo() {
271 return "An interactive demonstration of dithering.";
272 }
273
274 @Override
275 public String[][] getParameterInfo() {
276 String[][] info = {
277 { "xaxis", "{RED, GREEN, BLUE, ALPHA, SATURATION}",
278 "The color of the Y axis. Default is RED." },
279 { "yaxis", "{RED, GREEN, BLUE, ALPHA, SATURATION}",
280 "The color of the X axis. Default is BLUE." }
281 };
282 return info;
283 }
284 }
285
286
287 @SuppressWarnings("serial")
288 class DitherCanvas extends Canvas {
289
290 private Image img;
291 private static String calcString = "Calculating...";
292
293 @Override
294 public void paint(Graphics g) {
295 int w = getSize().width;
296 int h = getSize().height;
297 if (img == null) {
298 super.paint(g);
299 g.setColor(Color.black);
300 FontMetrics fm = g.getFontMetrics();
301 int x = (w - fm.stringWidth(calcString)) / 2;
302 int y = h / 2;
303 g.drawString(calcString, x, y);
304 } else {
305 g.drawImage(img, 0, 0, w, h, this);
306 }
307 }
308
309 @Override
310 public void update(Graphics g) {
311 paint(g);
312 }
313
314 @Override
315 public Dimension getMinimumSize() {
316 return new Dimension(20, 20);
317 }
318
319 @Override
320 public Dimension getPreferredSize() {
321 return new Dimension(200, 200);
322 }
323
324 public Image getImage() {
325 return img;
326 }
327
328 public void setImage(Image img) {
329 this.img = img;
330 repaint();
331 }
332 }
333
334
335 @SuppressWarnings("serial")
336 class DitherControls extends Panel implements ActionListener {
337
338 private CardinalTextField start;
339 private CardinalTextField end;
340 private Button button;
341 private Choice choice;
342 private DitherTest applet;
343 private static LayoutManager dcLayout = new FlowLayout(FlowLayout.CENTER,
344 10, 5);
345
346 public DitherControls(DitherTest app, int s, int e, DitherMethod type,
347 boolean vertical) {
348 applet = app;
349 setLayout(dcLayout);
350 add(new Label(vertical ? "Vertical" : "Horizontal"));
351 add(choice = new Choice());
352 for (DitherMethod m : DitherMethod.values()) {
353 choice.addItem(m.toString().substring(0, 1)
354 + m.toString().substring(1).toLowerCase());
355 }
356 choice.select(type.ordinal());
357 add(start = new CardinalTextField(Integer.toString(s), 4));
358 add(end = new CardinalTextField(Integer.toString(e), 4));
359 }
360
361
362 public void addRenderButton() {
363 add(button = new Button("New Image"));
364 button.addActionListener(this);
365 }
366
367
368 public int getParams(int vals[]) {
369 try {
370 vals[0] = scale(Integer.parseInt(start.getText()));
371 } catch (NumberFormatException nfe) {
372 vals[0] = 0;
373 }
374 try {
375 vals[1] = scale(Integer.parseInt(end.getText()));
376 } catch (NumberFormatException nfe) {
377 vals[1] = 255;
378 }
379 return choice.getSelectedIndex();
380 }
381
382
383 private int scale(int number) {
384 if (number < 0) {
385 number = 0;
386 } else if (number > 255) {
387 number = 255;
388 }
389 return number;
390 }
391
392
393 @Override
394 public void actionPerformed(ActionEvent e) {
395 if (e.getSource() == button) {
396 applet.start();
397 }
398 }
399 }
400
401
402 @SuppressWarnings("serial")
403 class CardinalTextField extends TextField {
404
405 String oldText = null;
406
407 public CardinalTextField(String text, int columns) {
408 super(text, columns);
409 enableEvents(AWTEvent.KEY_EVENT_MASK | AWTEvent.TEXT_EVENT_MASK);
410 oldText = getText();
411 }
412
413
414
415
416
417
418 @Override
419 protected void processEvent(AWTEvent evt) {
420 int id = evt.getID();
421 if (id != KeyEvent.KEY_TYPED) {
422 super.processEvent(evt);
423 return;
424 }
425
426 KeyEvent kevt = (KeyEvent) evt;
427 char c = kevt.getKeyChar();
428
429
430
431 if (Character.isDigit(c) || (c == '\b') || (c == '\u007f')) {
432 super.processEvent(evt);
433 return;
434 }
435
436 Toolkit.getDefaultToolkit().beep();
437 kevt.consume();
438 }
439
440
441
442
443
444
445
446
447
448 @Override
449 protected void processTextEvent(TextEvent te) {
450
451 String newText = getText();
452 if (newText.equals("") || textIsCardinal(newText)) {
453 oldText = newText;
454 super.processTextEvent(te);
455 return;
456 }
457
458 Toolkit.getDefaultToolkit().beep();
459 setText(oldText);
460 }
461
462
463
464 private boolean textIsCardinal(String textToCheck) {
465 try {
466 return Integer.parseInt(textToCheck, 10) >= 0;
467 } catch (NumberFormatException nfe) {
468 return false;
469 }
470 }
471 }